package sg.edu.nus.comp.cs4218.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Vector;

import sg.edu.nus.comp.cs4218.ITool;
import sg.edu.nus.comp.cs4218.IShell;
import sg.edu.nus.comp.cs4218.impl.fileutils.CatTool;
import sg.edu.nus.comp.cs4218.impl.fileutils.CdTool;
import sg.edu.nus.comp.cs4218.impl.fileutils.CopyTool;
import sg.edu.nus.comp.cs4218.impl.fileutils.DeleteTool;
import sg.edu.nus.comp.cs4218.impl.fileutils.EchoTool;
import sg.edu.nus.comp.cs4218.impl.fileutils.LsTool;
import sg.edu.nus.comp.cs4218.impl.fileutils.MoveTool;
import sg.edu.nus.comp.cs4218.impl.fileutils.PWDTool;

/**
 * The Shell is used to interpret and execute user's commands. 
 * Following sequence explains how a basic shell can be implemented in Java
 */
public class Shell implements IShell {
	public static String message;
	public static int statusCode;
	public static File workingDir;
	public static String stdin;
	private Runnable runnable;
	private static Thread thread;
	private static boolean pipe;
	private static boolean cd;

	/**
  * Parses the command and returns the corresponding ITool
  *
  * @param commandline
  *            - The command to parse
  * @return an ITool corresponding to the commandLine
  */
	@Override
	public ITool parse(String commandline) {
		if (commandline.contains("|")) {			
			return new PipingTool(getToolsFromCommand(commandline));
		}
		if (commandline.startsWith("pwd")) {
			return new PWDTool();
		} else if (commandline.startsWith("ls")) {
			return new LsTool();
		} else if (commandline.trim().startsWith("delete")) {
			return new DeleteTool(delimit(commandline));
		} else if (commandline.trim().startsWith("copy")) {
			return new CopyTool(delimit(commandline));
		} else if (commandline.trim().startsWith("move")) {
			return new MoveTool(delimit(commandline));
		} else if (commandline.trim().startsWith("echo")) {
			return new EchoTool(delimit(commandline));
		} else if (commandline.trim().startsWith("cd")) {
			cd = true;
			return new CdTool(delimit(commandline));
		} else if (commandline.trim().startsWith("cat")) {
			return new CatTool(delimit(commandline));
		}
		else if (commandline.trim().startsWith("grep")) {
			return new GrepTool(delimit(commandline));
		}
		else if (commandline.trim().startsWith("cut")) {
			return new CutTool(delimit(commandline));
		} else if (commandline.trim().startsWith("paste")) {
			return new PasteTool(delimit(commandline));
		} else if (commandline.trim().startsWith("comm")) {
			return new CommTool(delimit(commandline));
		} else if (commandline.trim().startsWith("sort")) {
			return new SortTool(delimit(commandline));
		} else if (commandline.trim().startsWith("uniq")) {
			return new UniqTool(delimit(commandline));
		} else if (commandline.trim().startsWith("wc")) {
			return new WCTool(delimit(commandline));
		} else if (commandline.trim().startsWith("ctrl-z")) {
			System.out.println("^Z");
			if (runnable != null) {
				stop(runnable);
			}
			return null;
		} else {
			// TODO Implement all other tools
			System.err.println("Cannot parse " + commandline);
			return null;
		}
	}

	/**
  * Creates a new thread and runs the tool in it
  *
  * @param tool 
  *            - The tool that will be run
  * @return a Runnable thread
  */
	@Override
	public Runnable execute(final ITool tool) {
		if (tool != null) {
			Runnable run = new Runnable() {
				@Override
				public void run() {
					message = tool.execute(workingDir, stdin);
					statusCode = tool.getStatusCode();
					if (cd == true && statusCode == 0) {
						workingDir = new File(message);
						cd = false;
					}
					if (pipe == false) {
						System.out.println(message);
					}
				}
			};
			return run;
		} else {
			return null;
		}
	}

	/**
  * Stopa the provided thread
  *
  * @param Runnable
  *            - The thread that will be stopped
  */
	@Override
	public void stop(Runnable toolExecution) {
		thread.interrupt();
	}

	/**
	 * Do Forever 
	 * 1. Wait for a user input 
	 * 2. Parse the user input. Separate the command and its arguments 
	 * 3. Create a new thread to execute the command
	 * 4. Execute the command and its arguments on the newly created thread.
	 * Exit with the status code of the executed command 
	 * 5. In the shell, wait for the thread to complete execution 
	 * 6. Report the exit status of the command to the user
	 * 
	 * @param args 
	 *            - arguments passed into program (currently not used)
	 * 
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws IOException,
			InterruptedException {
		pipe = false;
		cd = false;
		Shell shell = new Shell();
		workingDir = new File(System.getProperty("user.dir"));
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		while (true) {
			String command = br.readLine();
			if (!command.equals("exit")) {
				shell.runnable = shell.execute(shell.parse(command));
				thread = new Thread(shell.runnable);
				thread.start();
			} else {
				System.exit(0);
			}
		}
	}

	/**
	 * Parses a string by delimiting by 1) double quotes or 
	 * 2) spaces and removes the first element
	 * 
	 * @param toParse 
	 *            - The string to parse
	 * @return an array list of the parsed string
	 */
	private String[] delimit(String toParse) {
		ArrayList<String> parsedStdin = new ArrayList<String>();

		String regex = "\"([^\"]*)\"|(\\S+)";

		Matcher matcher = Pattern.compile(regex).matcher(toParse);

		while (matcher.find()) {
			if (matcher.group(1) != null) {
				parsedStdin.add(matcher.group(1));
			} else {
				parsedStdin.add(matcher.group(2));
			}
		}
		parsedStdin.remove(0);

		return parsedStdin.toArray(new String[parsedStdin.size()]);
	}

	/**
	 * Parses a string by delimiting by 1) double quotes or 2) spaces
	 * 
	 * @param toParse
	 *            - The string to parse
	 * @return an array list of the parsed string
	 */
	public static String[] parseIn(String toParse) {
		ArrayList<String> parsedStdin = new ArrayList<String>();

		String regex = "\"([^\"]*)\"|(\\S+)";

		Matcher matcher = Pattern.compile(regex).matcher(toParse);

		while (matcher.find()) {
			if (matcher.group(1) != null) {
				parsedStdin.add(matcher.group(1));
			} else {
				parsedStdin.add(matcher.group(2));
			}
		}

		return parsedStdin.toArray(new String[parsedStdin.size()]);
	}

	/**
	 * Determines if the path provided is an absolute or relative path and
	 * returns a file to be returned accordingly
	 * 
	 * @param workingDir
	 *            - The current working directory
	 * @param path
	 *            - The path provided
	 * @return A file representation of the path provided
	 */
	public static File getFile(File workingDir, String path) {
		File toReturn;

		if (path.contains(":")) {
			toReturn = new File(path);
		} else {
			toReturn = new File(workingDir, path);
		}

		return toReturn;
	}
	
	/**
	 * Delim commandline by "|" and create the tools
	 * 
	 * @param commandline
	 *            - user input command
	 * @return A vector of tools with their args set
	 */
	private Vector<ITool> getToolsFromCommand (String commandline) {
		Vector<ITool> tools = new Vector<ITool>();
		StringTokenizer st = new StringTokenizer(commandline, "|");
		while(st.hasMoreTokens()) {
			
			ITool tool = parse(st.nextToken());
			tools.add(tool);
		}
		
		return tools;
	}

}
